package crons

import (
	"sort"
	"time"

	"github.com/astaxie/beego"

	"github.com/gorhill/cronexpr"
)

//
//true:删除   false:不删除
type RemoveCheckFunc func(e *Entry) bool

// 用于调用多个cron
type Cron struct {
	// 任务列表
	entries []*Entry
	// 停止通道
	stop chan struct{}
	// 添加通道
	add chan *Entry
	// 删除通道
	remove chan RemoveCheckFunc
	//复制通道
	snapshot chan []*Entry
	// 该通是否正常运行
	running bool
}

// 开始程序
func (c *Cron) Start() {
	c.running = true
	go c.run()
}

//调用协程
func (c *Cron) run() {
	// 获取当前时间
	now := time.Now().Local()

	//遍历一边当前的任务
	for _, entry := range c.entries {
		entry.Next = entry.Schedule.Next(now)
	}

	for {
		//根据下一次的运行时间对任务进行排序
		sort.Sort(byTime(c.entries))

		var effective time.Time
		// 获取最近一次将要执行的时间
		if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
			effective = now.AddDate(10, 0, 0)
		} else {
			effective = c.entries[0].Next
		}

		select {
		// 最近一次的任务时间到达
		case now = <-time.After(effective.Sub(now)):

			beego.Info(effective)
			for _, e := range c.entries {
				if e.Next != effective {
					break

				}
				go e.Job.Run()
				e.Prev = e.Next
				e.Next = e.Schedule.Next(now)
			}
		//添加
		case newEntry := <-c.add:
			newEntry.Next = newEntry.Schedule.Next(now)
			c.entries = append(c.entries, newEntry)

		//删除
		case cb := <-c.remove:
			// 创建一个新切片用于存储未删除切片
			newEntries := make([]*Entry, 0)
			//遍历原始切片
			for _, e := range c.entries {
				//删除失败，将该任务追加到新的切片中
				if !cb(e) {
					newEntries = append(newEntries, e)
				}
			}

			// 赋值新的切片
			c.entries = newEntries
		//复制
		case <-c.snapshot:
			//beego.Info("c.snapshot")
			c.snapshot <- c.entrySnapshot()
		// 停止
		case <-c.stop:
			return
		}

		now = time.Now().Local()
	}
}

func (c *Cron) RemoveJob(cb RemoveCheckFunc) {
	c.remove <- cb
}

// spec  时间表达式
// cmd   执行的命令

func (c *Cron) AddJob(spec string, cmd Job) error {
	// 将原生的表达式转化成 Expression
	schedule, err := cronexpr.Parse(spec)
	if err != nil {
		return err
	}

	c.Schedule(*schedule, cmd)
	return nil
}

func (c *Cron) Schedule(schedule cronexpr.Expression, cmd Job) {
	entry := &Entry{
		Schedule: schedule,
		Job:      cmd,
	}

	//判断当前entries是否被别的协程占用
	if !c.running {
		c.entries = append(c.entries, entry)
	}
	c.add <- entry
}

// 复制任务列表
func (c *Cron) entrySnapshot() []*Entry {

	// beego.Info("entrySnapshot")
	newEntres := []*Entry{}
	//遍历原始数据
	for _, e := range c.entries {
		newEntres = append(newEntres, &Entry{
			Schedule: e.Schedule,
			Next:     e.Next,
			Prev:     e.Prev,
			Job:      e.Job,
		})
	}

	return newEntres
}

func (c *Cron) Entries() []*Entry {

	// beego.Info("entries")

	//c中的entries正在被别的线程操作
	if c.running {
		// beego.Info("running")
		c.snapshot <- nil
		entries := <-c.snapshot
		return entries
	}
	//返回entrySnapshot的数据
	return c.entrySnapshot()
}

func New() *Cron {
	return &Cron{
		//任务列表
		entries: nil,
		//停止的通道
		stop: make(chan struct{}),
		//添加通道
		add: make(chan *Entry),
		//删除通道
		remove: make(chan RemoveCheckFunc),
		//复制通道
		snapshot: make(chan []*Entry),
		//表示Cron是否正在运行
		running: false,
	}
}

// 每种定时任务执行的方式都不一样。所以定义一个借口
// 当我们创建定时任务时。，可以明确该定时任务该如何运行，
// 可以到那时在实现该方法。

type Job interface {
	Run()
}

// cron 定时任务
type Entry struct {
	//  执行时间列表 时间表达式
	Schedule cronexpr.Expression

	//下次执行时间
	Next time.Time

	//上次执行时间
	Prev time.Time

	Job
}

type byTime []*Entry

//求长度
func (s byTime) Len() int {
	return len(s)
}

//交换
func (s byTime) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

//比较大小
//如果下标i的数据小于为j 的数据。则返回true
// 不会调用 swap  记数据为升序
func (s byTime) Less(i, j int) bool {
	// 判断下标i的数据下一次运行 是否为time 类型的零值
	// 则返回true 说明该时间 不合法
	if s[i].Next.IsZero() {
		return false
	}
	if s[j].Next.IsZero() {
		return true
	}
	return s[i].Next.Before(s[j].Next)
}
